#include "cdc_acm_app.h"

#include "usbd_cdc.h"
#include "usbd_core.h"

/*!< endpoint address */
#define CDC_IN_EP 0x81
#define CDC_OUT_EP 0x02
#define CDC_INT_EP 0x83

#define USBD_VID 0x66CC
#define USBD_PID 0xFFFF
#define USBD_MAX_POWER 100
#define USBD_LANGID_STRING 1033

/*!< config descriptor size */
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN)

#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS 512
#else
#define CDC_MAX_MPS 64
#endif

/*!< global descriptor */
static const uint8_t cdc_descriptor[] = {
    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID,
                               0x0100, 0x01),
    USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01,
                               USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
    CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP,
                            CDC_MAX_MPS, 0x02),
    ///////////////////////////////////////
    /// string0 descriptor
    ///////////////////////////////////////
    USB_LANGID_INIT(USBD_LANGID_STRING),
    ///////////////////////////////////////
    /// string1 descriptor
    ///////////////////////////////////////
    0x14,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'C', 0x00,                  /* wcChar0 */
    'h', 0x00,                  /* wcChar1 */
    'e', 0x00,                  /* wcChar2 */
    'r', 0x00,                  /* wcChar3 */
    'r', 0x00,                  /* wcChar4 */
    'y', 0x00,                  /* wcChar5 */
    'U', 0x00,                  /* wcChar6 */
    'S', 0x00,                  /* wcChar7 */
    'B', 0x00,                  /* wcChar8 */
    ///////////////////////////////////////
    /// string2 descriptor
    ///////////////////////////////////////
    0x26,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'C', 0x00,                  /* wcChar0 */
    'h', 0x00,                  /* wcChar1 */
    'e', 0x00,                  /* wcChar2 */
    'r', 0x00,                  /* wcChar3 */
    'r', 0x00,                  /* wcChar4 */
    'y', 0x00,                  /* wcChar5 */
    'U', 0x00,                  /* wcChar6 */
    'S', 0x00,                  /* wcChar7 */
    'B', 0x00,                  /* wcChar8 */
    ' ', 0x00,                  /* wcChar9 */
    'C', 0x00,                  /* wcChar10 */
    'D', 0x00,                  /* wcChar11 */
    'C', 0x00,                  /* wcChar12 */
    ' ', 0x00,                  /* wcChar13 */
    'D', 0x00,                  /* wcChar14 */
    'E', 0x00,                  /* wcChar15 */
    'M', 0x00,                  /* wcChar16 */
    'O', 0x00,                  /* wcChar17 */
    ///////////////////////////////////////
    /// string3 descriptor
    ///////////////////////////////////////
    0x16,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    '2', 0x00,                  /* wcChar0 */
    '0', 0x00,                  /* wcChar1 */
    '2', 0x00,                  /* wcChar2 */
    '2', 0x00,                  /* wcChar3 */
    '1', 0x00,                  /* wcChar4 */
    '2', 0x00,                  /* wcChar5 */
    '3', 0x00,                  /* wcChar6 */
    '4', 0x00,                  /* wcChar7 */
    '5', 0x00,                  /* wcChar8 */
    '6', 0x00,                  /* wcChar9 */
#ifdef CONFIG_USB_HS
    ///////////////////////////////////////
    /// device qualifier descriptor
    ///////////////////////////////////////
    0x0a, USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, 0x00, 0x02, 0x02, 0x02, 0x01,
    0x40, 0x01, 0x00,
#endif
    0x00};

#define CDC_BUFFER_SIZE 2048

USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t cdc_read_buf[CDC_BUFFER_SIZE];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t cdc_write_buf[CDC_BUFFER_SIZE];

volatile bool ep_tx_busy_flag = false;

#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS 512
#else
#define CDC_MAX_MPS 64
#endif

void usbd_event_handler(uint8_t event) {
  switch (event) {
    case USBD_EVENT_RESET:
      break;
    case USBD_EVENT_CONNECTED:
      break;
    case USBD_EVENT_DISCONNECTED:
      break;
    case USBD_EVENT_RESUME:
      break;
    case USBD_EVENT_SUSPEND:
      break;
    case USBD_EVENT_CONFIGURED:
      /* setup first out ep read transfer */
      usbd_ep_start_read(CDC_OUT_EP, cdc_read_buf, CDC_BUFFER_SIZE);
      break;
    case USBD_EVENT_SET_REMOTE_WAKEUP:
      break;
    case USBD_EVENT_CLR_REMOTE_WAKEUP:
      break;

    default:
      break;
  }
}

void usbd_cdc_acm_bulk_out(uint8_t ep, uint32_t nbytes) {
  // USB_PRINT("actual out len:%d\r\n", nbytes);
  // user code to process the input data
  cdc_acm_data_recv_callback(cdc_read_buf, nbytes);
  // start next read
  usbd_ep_start_read(CDC_OUT_EP, cdc_read_buf, CDC_BUFFER_SIZE);
}

void usbd_cdc_acm_bulk_in(uint8_t ep, uint32_t nbytes) {
  // USB_PRINT("actual in len:%d\r\n", nbytes);
  if ((nbytes % CDC_MAX_MPS) == 0 && nbytes) {
    /* send zlp */
    usbd_ep_start_write(CDC_IN_EP, NULL, 0);
  } else {
    ep_tx_busy_flag = false;
    cdc_acm_data_send_cplt_callback();
  }
}

/*!< endpoint call back */
struct usbd_endpoint cdc_out_ep = {.ep_addr = CDC_OUT_EP,
                                   .ep_cb = usbd_cdc_acm_bulk_out};

struct usbd_endpoint cdc_in_ep = {.ep_addr = CDC_IN_EP,
                                  .ep_cb = usbd_cdc_acm_bulk_in};

static struct usbd_interface intf0;
static struct usbd_interface intf1;

void cdc_acm_init(void) {
  usbd_desc_register(cdc_descriptor);
  usbd_add_interface(usbd_cdc_acm_init_intf(&intf0));
  usbd_add_interface(usbd_cdc_acm_init_intf(&intf1));
  usbd_add_endpoint(&cdc_out_ep);
  usbd_add_endpoint(&cdc_in_ep);
  usbd_initialize();
}

volatile uint8_t dtr_enable = 0;

void usbd_cdc_acm_set_dtr(uint8_t intf, bool dtr) {
  if (dtr) {
    dtr_enable = 1;
  } else {
    dtr_enable = 0;
  }
  ep_tx_busy_flag = false;
}

bool cdc_acm_connected(void) { return dtr_enable; }

bool cdc_acm_idle(void) { return (!ep_tx_busy_flag) && dtr_enable; }

uint8_t cdc_acm_data_send(uint8_t *buf, uint32_t len) {
  if (!dtr_enable) return 1;
  if (ep_tx_busy_flag) return 2;
  ep_tx_busy_flag = true;
  memcpy(cdc_write_buf, buf, len);
  if (usbd_ep_start_write(CDC_IN_EP, cdc_write_buf, len) != 0) {
    ep_tx_busy_flag = false;
    return 3;
  }
  return 0;
}

uint8_t *cdc_acm_data_send_raw_acquire(uint32_t *len) {
  if (!dtr_enable) return NULL;
  if (ep_tx_busy_flag) return NULL;
  ep_tx_busy_flag = true;
  *len = CDC_BUFFER_SIZE;
  return cdc_write_buf;
}

uint8_t cdc_acm_data_send_raw_commit(uint32_t len) {
  if (len == 0) {
    ep_tx_busy_flag = false;
    return 0;
  }
  if (usbd_ep_start_write(CDC_IN_EP, cdc_write_buf, len) != 0) {
    ep_tx_busy_flag = false;
    return 3;
  }
  return 0;
}

__attribute__((weak)) void cdc_acm_data_send_cplt_callback(void) {}

__attribute__((weak)) void cdc_acm_data_recv_callback(uint8_t *buf,
                                                      uint32_t len) {
  (void)buf;
  (void)len;
}

void usb_dc_low_level_init(void) {
  extern PCD_HandleTypeDef hpcd_USB_OTG_FS;
  hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
  HAL_PCD_MspInit(&hpcd_USB_OTG_FS);
}
